package com.ruoyi.quartz.task.base;

import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import com.ruoyi.common.enums.JobRunningStatus;
import com.ruoyi.quartz.domain.JobInfoConfig;
import com.ruoyi.quartz.domain.JobLogDTO;
import com.ruoyi.quartz.domain.ReturnJson;
import com.ruoyi.quartz.service.BaseJobService;
import com.ruoyi.quartz.util.DateUtil;
import com.ruoyi.quartz.util.HttpUtils;
import com.ruoyi.quartz.util.StringUtils;

import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class BaseJob implements SchedulingConfigurer {

	@Autowired
	private BaseJobService baseJobService;
	
	@Autowired
	DataSourceTransactionManager transactionManager;
	
	@Value("${job.jobPoolSize.threadPoolSize}")
	public int threadPoolSize;

	public static String cron;
	
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		if(taskRegistrar.getScheduler() == null) {
			taskRegistrar.setScheduler(Executors.newScheduledThreadPool(threadPoolSize));
		}
		// 项目部署时，会在这里执行一次，从数据库拿到cron表达式
		String jobName = getJobName();
		String instanceName = getCurrentInstanceName();
		Map<String, String> params = new HashMap<String, String>();
		params.put("jobName", jobName);
		params.put("instanceName", instanceName);

		Runnable task = new Runnable() {
			@Override
			public void run() {
				// 执行任务
				execute(jobName, instanceName);
			}

		};
		Trigger trigger = new Trigger() {
			@Override
			public Date nextExecutionTime(TriggerContext triggerContext) {
				// 任务触发，可修改任务的执行周期.
				// 每一次任务触发，都会执行这里的方法一次，重新获取下一次的执行时间
				cron = baseJobService.getCronTime(params);
				if (StringUtils.isEmpty(cron)) {
					log.error("job_name：{}，cron表达式没有在sys_job_info_config表中配置！", jobName);
					return null;
				}
				CronTrigger trigger = new CronTrigger(cron);
				Date nextExec = trigger.nextExecutionTime(triggerContext);
				log.info("job_name：{}，instance_name：{}，cron：{}，下次执行时间：{}", jobName, instanceName, cron, DateUtil.format(nextExec));
				return nextExec;
			}

		};
		taskRegistrar.addTriggerTask(task, trigger);
	}

	/**
	 * 任务执行调用
	 * @param jobName
	 * @param instanceName
	 */
	private void execute(String jobName, String instanceName) {
		boolean isLocked = false;
		Long lockJobId = 0L;
		Long jobLogId = 0L;
		String errorMsg = null;
		String status = JobRunningStatus.wait.getValue();
		Date startDate = new Date(); // 获取开始时间
		JobInfoConfig config = new JobInfoConfig();//当前机器名称的job配置
		try {
			// 1、job执行前校验
			if (!preCheck(jobName, instanceName, config)) {
				return;
			}

			// 2.锁住任务
			lockJobId = lock(jobName, instanceName, status, config);
			if(lockJobId == null) {
				return;
			}
			// 3、记录job开始
			log.info("job_name：{}，instance_name：{}，启动...", jobName, instanceName);
			jobLogId = insertJobLog(jobName, instanceName, status, DateUtil.formatHighScale(startDate));
			isLocked = true;
			
			// 4. 执行具体的任务逻辑代码部分，放在具体JOB中执行
			ReturnJson result = doWork();
			if(result == null) {
				errorMsg = jobName + "：返回ReturnJson为null";
				status = JobRunningStatus.fail.getValue();
			}else if(result.isSuccess()) {
				status = JobRunningStatus.success.getValue();
			}else {
				errorMsg = result.getMessage();
				status = JobRunningStatus.fail.getValue();
			}
			// 5、解锁
			unlock(lockJobId, status);
		} catch (Exception e) {
			status = JobRunningStatus.fail.getValue();
			if (isLocked) {
				unlock(lockJobId, status);
			}
			errorMsg = e.getMessage();
			log.error("job_name：{}，instance_name：{}：运行异常：{}", jobName, instanceName, errorMsg);
		}
		// 6、记录job完成
		Date endDate = new Date(); // 获取结束时间
		String cost = String.valueOf(endDate.getTime() - startDate.getTime());
		updateJobLog(jobLogId, status,  DateUtil.formatHighScale(endDate), cost, errorMsg);
		log.info("job_name：{}，instance_name：{}，执行结束...状态：{}，耗时：{} ms", jobName, instanceName, status, cost);
	}

	/**
	 * JOB执行前校验
	 * @param jobName
	 * @param instanceName
	 * @param config 
	 * @return
	 */
	private boolean preCheck(String jobName, String instanceName, JobInfoConfig config) {
		Map<String, String> paramMap = new HashMap<String, String>();
		paramMap.put("jobName", jobName);
		paramMap.put("instanceName", instanceName);
		
		List<JobInfoConfig> jobConfigs = baseJobService.getJobConfig(paramMap);
		if (StringUtils.isEmpty(jobConfigs)) {
			log.info("jobName：{}，instanceName：{}，在sys_job_info_config表没有配置！", jobName, instanceName);
			return false;
		} 
		
		//表中是否配置机器名称，如果全部没有配，则是抢占式JOB，否则再校验当前机器名称是否配置
		boolean flag = true;
		boolean isExistInstanceName = false;
		for (JobInfoConfig jobInfoConfig : jobConfigs) {
			if(StringUtils.isEmpty(jobInfoConfig.getInstanceName())) {
				continue;
			}
			flag = false;
			if(instanceName.equals(jobInfoConfig.getInstanceName())) {
				isExistInstanceName = true;
				config.setIsrunning(jobInfoConfig.getIsrunning());
				config.setInstanceName(instanceName);
				config.setId(jobInfoConfig.getId());
				break;
			}
		}
		//全部没配置机器名称，抢占式JOB，无需校验机器名称和机器配置信息
		if(flag) {
			return true;
		}
		//isExistInstanceName=false，当前机器名称没有配置，校验失败
		if (!isExistInstanceName) {
			log.info("instanceName：{}，在sys_job_info_config表没有配置！", instanceName);
			return false;
		}
		if ("1".equals(config.getIsrunning())) {
			log.info("jobName：{}，instanceName：{}，正在运行，无法重复执行！", jobName, instanceName);
			return false;
		}
		String isExists = baseJobService.checkInstanceConfig(paramMap);
		if (!"1".equals(isExists)) {
			log.info("instanceName：{}，在sys_instance_info_config表没有配置！", instanceName);
			return false;
		}
		return true;
	}

	/**
	 * 给job加锁
	 * @param jobName
	 * @param instanceName
	 * @param status
	 * @param config 
	 * @return
	 * @throws Exception 
	 */
	private Long lock(String jobName, String instanceName, String status, JobInfoConfig config) throws Exception {
		Long jobId = null;
		
		TransactionStatus transactionStatus = null;
		try {
			transactionStatus = getTransactionStatus(false);
			Long lockId = baseJobService.lockJobConfig("JobLock");
			if (lockId == null) {
				throw new Exception("jobName：" + jobName + "，instanceName：" + instanceName + "，未获得锁");
			}
			Map<String,Object> updateMap = new HashMap<String,Object>();
			updateMap.put("jobName", jobName);
			if(StringUtils.isNotEmpty(config.getInstanceName())) {
				updateMap.put("instanceName", instanceName);
			}
			jobId = baseJobService.getJobLimitOne(updateMap);
			if(jobId == null) {
				throw new Exception("jobName：" + jobName + "，instanceName：" + instanceName + "，当前任务已无效，或正在执行中");
			}
			updateMap.put("isrunning", "1");
			updateMap.put("jobId", jobId);
			updateMap.put("status", status);
			baseJobService.updateJobConfigById(updateMap);
			transactionManager.commit(transactionStatus);
		} catch (Exception e) {
			log.error("jobName：{}，加锁异常：{}", jobName, e.getMessage());
			transactionManager.rollback(transactionStatus);
			throw new Exception(jobName +"加锁异常");
		}
		return jobId;
	}
	
	/**
	 * 获取事务状态
	 * @param readOnly 是否只读事务
	 * @return
	 */
	private TransactionStatus getTransactionStatus(boolean readOnly) {
		DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
		transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
		transactionDefinition.setReadOnly(readOnly);
		TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
		return transactionStatus;
	}

	/**
	 * job解锁
	 * @param lockJobId
	 * @param status
	 */
	private void unlock(Long lockJobId, String status) {
		Map<String, Object> updateMap = new HashMap<String, Object>();
		updateMap.put("jobId", lockJobId);
		updateMap.put("isrunning", "0");
		updateMap.put("status", status);
		baseJobService.updateJobConfigById(updateMap);
	}

	/**
	 * 记录job日志
	 * @param jobName
	 * @param instanceName
	 * @param status
	 * @param startDate
	 * @return
	 */
	private Long insertJobLog(String jobName, String instanceName, String status, String startDate) {
		JobLogDTO logDTO = new JobLogDTO(jobName, instanceName, status, startDate, null, "0");
		baseJobService.insertJobLog(logDTO);
		return logDTO.getId();
	}

	/**
	 * 更新job日志
	 * @param jobLogId
	 * @param status
	 * @param executeTime
	 * @param errorMsg 
	 */
	private void updateJobLog(Long jobLogId, String status, String endDate, String executeTime, String errorMsg) {
		JobLogDTO logDTO = new JobLogDTO(jobLogId, status, endDate, executeTime, errorMsg);
		baseJobService.updateJobLogByIdlogDTO(logDTO);
	}
	
	/**
	 * 获取当前机器名称
	 * @return
	 */
	private String getCurrentInstanceName() {
		String instanceName = "";
		try {
			instanceName = HttpUtils.getHostName();
		} catch (UnknownHostException e) {
			log.error("job_name：{}获取机器名称异常！", getJobName());
		}
		return instanceName;
	}

	/**
	 * 获取JOB名称，由子类实现
	 * 
	 * @return
	 */
	public abstract String getJobName();

	/**
	 * job具体业务逻辑，由子类实现
	 * @return 
	 */
	public abstract ReturnJson doWork();

}
